#include "../include/func.h"

// 客户端启动需要的信息初始化
int getValidStr(char *input, char *str) {
    char *p; 
    char s[] = {' ', '\n'};
    p = strtok(input, s);
    p = strtok(NULL, s);
    p = strtok(NULL, s);
    strcpy(str, p); 
    return 0;
}

int getClientConf(char *fileconf, char *ip, char *port) {
    FILE *pconf = fopen(fileconf, "r");
    ERROR_CHECK(pconf, NULL, "open conf file failed");
    char buf[128] = {0};
    fgets(buf, sizeof(buf), pconf);
    getValidStr(buf, ip);
    bzero(buf, sizeof(buf));
    fgets(buf, sizeof(buf), pconf);
    getValidStr(buf, port);
    fclose(pconf);
    return 0;
}


int epollAdd(int epfd, int fd) {
    struct epoll_event ev; 
    ev.events = EPOLLIN;
    ev.data.fd = fd; 
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    return 0;
}

int epollDel(int epfd, int fd) {
    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
    return 0;
}


//将line分割，存入cmd
void init_cmd(char *line, pcmd_t pcmd)
{
    //使用strtok将line拆分
    char *token;
    const char s[] = {' ', '\n'};

    //首次使用strtok时，需传入待分割的字符串和分隔符集合
    //之后再调用，第一个参数设为NULL，表示从继续上次的位置分割
    token = strtok(line, s);
    while (NULL != token) {
        strcpy(pcmd->cmd[pcmd->argc++], token);
        token = strtok(NULL, s);
    }
}

//打印命令
void print_cmd(pcmd_t pcmd)
{
    printf("cmd: ");
    for (int i = 0; i < pcmd->argc; ++i) {
        printf("%s ", pcmd->cmd[i]);
    }
    printf("\n");
}


//检查命令是否合法
int cmd_check(pcmd_t pcmd, int *ptag)
{
    int flag = -1;
    char tmp[64] = "";
    strncpy(tmp, pcmd->cmd[0], 64);
    if (strcmp(tmp, "cd") == 0) {
        *ptag = TAGCD;
        flag = 0;
    }
    if (strcmp(tmp, "ls") == 0) {
        *ptag = TAGLS;
        flag = 0;
    }
    if (strcmp(tmp, "pwd") == 0) {
        *ptag = TAGPWD;
        flag = 0;
    }
    if (strcmp(tmp, "rm") == 0) {
        *ptag = TAGREMOVE;
        flag = 0;
    }
    if (strcmp(tmp, "puts") == 0) {
        *ptag = TAGPUTS;
        flag = 0;
    }
    if (strcmp(tmp, "gets") == 0) {
        *ptag = TAGGETS;
        flag = 0;
    }
    if (strcmp(tmp, "mkdir") == 0) {
        *ptag = TAGMKDIR;
        flag = 0;
    }
    if (strcmp(tmp, "rmdir") == 0) {
        *ptag = TAGRMDIR;
        flag = 0;
    }
    return flag;
}


// 接收命令行执行结果
int recv_ls(socket_t fd_server, ptrain_t ptrain) {
    memset(ptrain, 0, sizeof(train_t));
    // 先接路径
    recv(fd_server, &ptrain->data_length, sizeof(ptrain->data_length), MSG_WAITALL);
    recv(fd_server, ptrain->data, ptrain->data_length, MSG_WAITALL);
    printf("%s\n", ptrain->data);
    while (1) {
        memset(ptrain, 0, sizeof(train_t));
        recv(fd_server, &ptrain->data_length, sizeof(ptrain->data_length), MSG_WAITALL);
        if (ptrain->data_length == 0) {
            break;
        }
        recv(fd_server, ptrain->data, ptrain->data_length, MSG_WAITALL);
        printf("%s", ptrain->data);
    }
    printf("\n");
    return 0;
}

int recv_pwd(socket_t fd_server, ptrain_t ptrain) {
    memset(ptrain, 0, sizeof(train_t));
    recv(fd_server, &ptrain->data_length, sizeof(ptrain->data_length), MSG_WAITALL);
    recv(fd_server, ptrain->data, ptrain->data_length, MSG_WAITALL);
    printf("current dir: %s\n\n", ptrain->data);
    return 0;
}

int recv_cd(socket_t fd_server, ptrain_t ptrain) {
    memset(ptrain, 0, sizeof(train_t));
    recv(fd_server, &ptrain->data_length, sizeof(ptrain->data_length), MSG_WAITALL);
    recv(fd_server, ptrain->data, ptrain->data_length, MSG_WAITALL);
    printf("current dir: %s\n\n", ptrain->data);
    return 0;
}

int recv_rm(socket_t fd_server, ptrain_t ptrain) {
    memset(ptrain, 0, sizeof(train_t));
    recv(fd_server, &ptrain->data_length, sizeof(ptrain->data_length), MSG_WAITALL);
    recv(fd_server, ptrain->data, ptrain->data_length, MSG_WAITALL);
    printf("result: %s\n", ptrain->data);
    return 0;
}

int recv_mkdir(socket_t fd_server, ptrain_t ptrain) {
    memset(ptrain, 0, sizeof(train_t));
    recv(fd_server, &ptrain->data_length, sizeof(ptrain->data_length), MSG_WAITALL);
    recv(fd_server, ptrain->data, ptrain->data_length, MSG_WAITALL);
    printf("result: %s\n", ptrain->data);
    return 0;
}

int recv_rmdir(socket_t fd_server, ptrain_t ptrain) {
    memset(ptrain, 0, sizeof(train_t));
    recv(fd_server, &ptrain->data_length, sizeof(ptrain->data_length), MSG_WAITALL);
    recv(fd_server, ptrain->data, ptrain->data_length, MSG_WAITALL);
    printf("result: %s\n", ptrain->data);
    return 0;
}

int recv_puts(socket_t fd_server, ptrain_t ptrain, pcmd_t pcmd) {
    // 向服务器发送文件，文件名是客户端命令第二个参数
    int ret = -1;
    char filename[30] = {0};
    strcpy(filename, pcmd->cmd[1]);
    // 发送文件大小
    off_t filesize = 0;
    printf("puts file %s\n", filename);

    // 在这里确保文件能正常打开再进入下一步
    struct stat st;
    stat(filename, &st);
    filesize = st.st_size;
    //文件类型不是常规的，就将文件大小设为0
    if (filesize && S_IFREG != (st.st_mode & S_IFMT)) {
        filesize = 0;
    }

    //发送文件大小，让客户端选择传输方式
    send(fd_server, &filesize, sizeof(int), 0);

    if (0 == filesize) {
        //没找到，退出
        printf("Not found the file!\n");
        return -1;
    }
    else if (filesize > HUNDRED_MEGABYTE * 10) {
        ret = send_file_sendfile(fd_server, filename, filesize); //>100M, 用零拷贝接口
    }
    else {
        ret = send_file_train(fd_server, filename); //用普通方式发送
    }
    //判断传输结果
    if (-2 == ret) {
        //端意外断开，返回-2
        return -2;
    }
    else if (0 == ret) {
        //正常传输完毕，通知服务端
        sprintf(ptrain->data, "file [%s] upload succeed!", filename);
        puts(ptrain->data);
    }
    //回应服务端
    ptrain->tag = TAG_RESPONSEPUTS;
    send(fd_server, &ptrain->tag, sizeof(int), 0);
    ptrain->data_length = sizeof(ptrain->data);
    send(fd_server, &ptrain->data_length, sizeof(int), 0);
    send(fd_server, ptrain->data, ptrain->data_length, 0);
    return 0;
}

int recv_gets(socket_t fd_server, ptrain_t ptrain, pcmd_t pcmd) {
    // 客户端从服务器接收文件
    int ret = -1;
    off_t filesize = 0;
    // 接收文件大小
    recv(fd_server, &filesize, sizeof(int), MSG_WAITALL);
    if (0 == filesize) {
        //文件有问题无法接收，退出
        return -1;
    }
    else {
        if (get_filesize(pcmd->cmd[1])){
            //证明文件已经存在，实现极速快传
            ret = 0;
        } else {
            ret = recv_file_train(fd_server, pcmd->cmd[1], filesize); //用普通方式接收
        }
    }
    if (ret == -2) {
        return -2;
    } else if (ret == 0) {
        printf("transfile finish\n");
    }
    // 传输结束与服务端通信
    memset(ptrain, 0, sizeof(train_t));
    recv(fd_server, &ptrain->tag, sizeof(int), MSG_WAITALL);
    recv(fd_server, &ptrain->data_length, sizeof(int), MSG_WAITALL);
    recv(fd_server, &ptrain->data, ptrain->data_length, MSG_WAITALL);
    printf("%s\n", ptrain->data);
    return 0;
}


//
int send_file_train(int socket_fd, char *filename)                                                          
{
    //打开传输文件
    int file_fd = open(filename, O_RDWR);
    ERROR_CHECK(file_fd, -1, "open");
    train_t train;   //定义一个小货车，用来传输文件
    int ret = -1;

    //断点续传
    //从客户端接收文件大小，如果大于0就偏移文件指针
    int offset = 0;
    recv(socket_fd, &offset, sizeof(int), 0);
    if (offset) {
        lseek(file_fd, offset, SEEK_SET);
    }

    //发文件内容
    while (1) {
        memset(train.data, 0, sizeof(train.data));
        train.data_length = read(file_fd, train.data, sizeof(train.data));
        ERROR_CHECK(train.data_length, -1, "read");
        if (0 == train.data_length) {
            //传输完成，通知客户端，然后退出循环
            ret = send(socket_fd, &train.data_length, sizeof(int), 0);
            ERROR_CHECK(ret, -1, "send");
            //关闭传输文件
            close(file_fd);
            return 0;
        }
        //判断客户端异常异常退出
        ret = send(socket_fd, &train.data_length, sizeof(int), 0);
        if (-1 == ret) {
            //客户端异常断开，退出循环
            printf("Client Abnormal Exit!\n");
            //关闭传输文件
            close(file_fd);
            return -2;
        }
        ret = send(socket_fd, train.data, train.data_length, 0);
        if (-1 == ret) {
            //客户端异常断开，退出循环
            printf("Client Abnormal Exit!\n");
            //关闭传输文件
            close(file_fd);
            return -2;
        }
    }
}

int get_filesize1(char *filename) {
    int size = -1;
    char dir_path[256] = {0};
    char file_name[256] = {0};
    
    // 分离目录路径和文件名
    char *last_slash = strrchr(filename, '/');
    if (last_slash) {
        strncpy(dir_path, filename, last_slash - filename);
        strcpy(file_name, last_slash + 1);
    } else {
        strcpy(dir_path, ".");
        strcpy(file_name, filename);
    }
    
    DIR *dirp = opendir(dir_path);
    if (!dirp) {
        perror("opendir");
        return -1;
    }
    
    struct dirent *pcur;
    while ((pcur = readdir(dirp)) != NULL) {
        if (strcmp(file_name, pcur->d_name) == 0) {
            char full_path[512];
            snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, pcur->d_name);
            
            struct stat st;
            if (stat(full_path, &st) == 0) {
                printf("File exists!\n");
                printf("File size: %ld bytes\n", st.st_size);
                size = (int)st.st_size;
            } else {
                perror("stat");
            }
            break;
        }
    }
    
    closedir(dirp);
    return size;
}

//获取文件大小
int get_filesize(char *filename /*, pdir_stack_t pdir_stack*/)
{
    //遍历此目录下的所有文件
    int size = 0;
    //to_do 需要把目录改成工作路径
    DIR *dirp = opendir("/home/zyn/cloud_disk_project_2.1/client/bin");
    struct dirent *pcur;
    while ((pcur = readdir(dirp)) != NULL) {
        if (!strcmp(filename, pcur->d_name)) {
            struct stat st;
            stat(filename, &st);
            printf("file exist!\n");
            printf("this file size: %ld\n", st.st_size);
            size = st.st_size;
            break;
        }
    }

    closedir(dirp);
    return size;
}
// 定义进度条宽度
#define PROGRESS_BAR_WIDTH 25

// 显示进度条的函数
void show_progress(off_t current, off_t total) {
    if (total <= 0) return;  // 避免除以零

    double percentage = (double)current / total;
    int pos = (int)(percentage * PROGRESS_BAR_WIDTH);

    printf("[");
    for (int i = 0; i < PROGRESS_BAR_WIDTH; ++i) {
        if (i < pos) printf("=");
        else if (i == pos) printf(">");
        else printf(" ");
    }
    printf("] %.2f%%\r", percentage * 100);
    /* printf("] %.2f%% (%ld/%ld bytes)\r", percentage * 100, current, total); */
    fflush(stdout);
}

//小于100M的接收方式
int recv_file_train(int fd_socket, char *filename, off_t filesize)
{
    train_t train;
    int ret = -1;
    char temp_filename[256] = {0};
    //断点续传
    //在本地找是否有这个文件, 有发送文件大小，没有发送0
    //filename后拼接.filepart
    strcpy(temp_filename, filename);
    strcat(temp_filename, ".filepart");
    off_t offset = get_filesize(temp_filename);
    /* printf("offset = %ld\n", offset); */
    send(fd_socket, &offset, sizeof(int), 0);
    //打开文件，并偏移文件指针
    int fd_file = open(temp_filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
    ERROR_CHECK(fd_file, -1, "open");
    if (offset) {
        /* printf("offset = %ld\n", offset); */
        lseek(fd_file, offset, SEEK_SET);
    }

    off_t total_size = filesize;
    off_t current_size = offset;
    off_t last_size = current_size;                                              
    off_t slice = filesize / 100;
    //循环接收文件内容
    while (1) {
        //重置小货车
        memset(&train, 0, sizeof(train));
        //先接数据长度
        recv(fd_socket, &train.data_length, sizeof(int), 0);
        /* printf("len = %d\n", train.data_length); */
        if (train.data_length == 0 && current_size == total_size) {
            //传输完毕
            printf("[========================>] 100.00%%\n");
            //关闭文件
            close(fd_file);
            //修改文件名，去掉.filepart的后缀
            int rret = rename(temp_filename, filename);
            if(rret == -1){
                perror("rename failed");
                return -3;
            }
            return 0;
        }

        //根据数据长度接收数据内容
        //防止发送方发的慢，导致接收缓冲区将车厢当成车头，设置recv参数为MSG_WAITA
        ret = recv(fd_socket, train.data, train.data_length, MSG_WAITALL);
        if (0 == ret) {
            //对端异常断开
            close(fd_file);
            return -2;
        }
        //将接收数据写入文件
        write(fd_file, train.data, ret);
        current_size += ret;
        // 更新当前大小并显示进度
        if (current_size - last_size >= slice) {
            show_progress(current_size, total_size);
            last_size = current_size;
        }
    }
}

// 大文件先mmap
int send_file_sendfile(int fd_socket, char *filename, off_t filesize){
    // 打开传输文件
    int file_fd = open(filename, O_RDWR);
    ERROR_CHECK(file_fd, -1, "open");
    train_t train;   //定义一个小货车，用来传输文件
    int ret = -1;
    // mmap
    char *p = (char*)mmap(NULL, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd , 0);
    ERROR_CHECK(p, NULL, "mmap");

    //断点续传
    //从接收方获得文件目前大小，如果大于0就偏移文件指针
    int offset = 0;
    recv(fd_socket, &offset, sizeof(int), 0);
    if (offset) {
        lseek(file_fd, offset, SEEK_SET);
    }

    // 累计发送数据长度
    int send_length = 0;
    //发文件内容
    while (1) {                                                                  
        memset(&train.data_length, 0, sizeof(train.data_length));
        unsigned long rest = filesize - send_length;
        train.data_length = sizeof(train.data);
        if (rest < sizeof(train.data)) {
            train.data_length = rest;
        }   
        if (0 == train.data_length) {
            //传输完成，通知接收方，然后退出循环
            ret = send(fd_socket, &train.data_length, sizeof(int), 0);
            ERROR_CHECK(ret, -1, "send");
            //关闭传输文件
            munmap(p, filesize);
            close(file_fd);
            return 0;
        }

        //判断客户端异常异常退出
        ret = send(fd_socket, &train.data_length, sizeof(int), 0);
        if (-1 == ret) {
            //客户端异常断开，退出循环
            printf("Client Abnormal Exit!\n");
            //关闭传输文件
            munmap(p, filesize);
            close(file_fd);
            return -2;
        }
        ret = send(fd_socket, p /*+ offset*/ + send_length, train.data_length, 0);
        if (-1 == ret) {
            //客户端异常断开，退出循环
            printf("Client Abnormal Exit!\n");
            //关闭传输文件
            munmap(p, filesize);
            close(file_fd);
            return -2;
        }
        send_length += train.data_length;
    }

    return 0;
}



int login_client(int sockfd)
{
    train_t train;
    char user[64];
    char inputpassword[128];
    char salt[31];
    bzero(user, 64);
    bzero(inputpassword, 128);
    bzero(salt, 31);
    printf("Please input user name.\n");
    while(1){
        // 发送用户名
        printf("user:");
        fflush(stdout);
        scanf("%s", user);
        memset(&train, 0, sizeof(train_t));
        train.tag = TAGCD;
        train.data_length = sizeof(user);
        memcpy(&train.data, &user, sizeof(user));
        send(sockfd, &train.tag, sizeof(train.tag), 0);
        send(sockfd, &train.data_length, sizeof(train.data_length), 0);
        send(sockfd, &train.data, train.data_length, 0);
        // 检查用户名是否存在
        recv(sockfd, &train.tag, sizeof(int), MSG_WAITALL);
        if(train.tag == 1){// 用户名正确
            break;
        }else if(train.tag == 0){
            printf("username is wrong\n");
            continue;
        }
    }
    // 接收服务端盐值
    memset(&train, 0, sizeof(train_t));
    recv(sockfd, &train.tag, sizeof(train.tag), MSG_WAITALL);
    recv(sockfd, &train.data_length, sizeof(train.data_length), MSG_WAITALL);
    recv(sockfd, &train.data, train.data_length, MSG_WAITALL);
    strcpy(salt, train.data);
    /* printf("salt:%s\n", salt); */
    printf("Please input user password.\n");
    while(1){
        // 用盐值加密输入的密码并发送
        printf("password:");
        fflush(stdout);
        scanf("%s", inputpassword);
        // salt盐值，inputpassword输入的密码,加密后的密文encrypted_password
        char encrypted_password[128];
        bzero(encrypted_password, 128);
        strcpy(encrypted_password, crypt(inputpassword, salt));
        /* printf("send to server:%s\n", encrypted_password); */
        memset(&train, 0, sizeof(train_t));
        train.tag = TAGCD;
        train.data_length = sizeof(encrypted_password);
        memcpy(&train.data, encrypted_password, sizeof(encrypted_password));
        send(sockfd, &train.tag, sizeof(train.tag), 0);
        send(sockfd, &train.data_length, sizeof(train.data_length), 0);
        send(sockfd, &train.data, train.data_length, 0);
        // 检查密码是否正确
        recv(sockfd, &train.tag, sizeof(int), MSG_WAITALL);
        if(train.tag == 1){// 密码正确
            break;
        }else if(train.tag == 0){
            printf("password is wrong\n");
            continue;
        }
    }
    return 0;
}


